﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using System.Diagnostics;
using System.Windows.Forms;
using IDataObject = System.Runtime.InteropServices.ComTypes.IDataObject;
using System.IO;
using System.Xml;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using ZedGraph;

namespace Dazzle {
    [ComVisible(true)]
    [Guid("BF9DC749-0A32-4C39-AF89-68DB8BA3C5DD")]
    public class GraphBox : IDataObject, IOleObject, IPersistStorage, IOleInPlaceObject, IOleInPlaceActiveObject {
        public static Guid CLSID { get { return Win32.GetGuidAttribute(typeof(GraphBox)); } }

        private ILog logger = LogManager.GetLogger(typeof(GraphBox));

        public const string USER_TYPE = "Gildata Word Graph Box";
        public const string USER_TYPE_LONG = "Gildata Word Graph with matlab";

        // Manages our communication with the container.
        private IOleClientSite _clientSite;

        // I tried to use the OLE advise holder in dataAdviseThread, but it
        // hung mysteriously during shutdown.  Something was blocking the
        // thread.
        private IList<IAdviseSink> _advises = new List<IAdviseSink>();

        private DataAdviseThread _dataAdviseThread;

        public string GraphType {
            get { return ""; }
            set { }
        }

        // Can render box in various formats.  Implements most of IDataObject.
        private GraphRenderer _renderer;

        // If we are being edited then this is our (MDI child) form.
        private Form _form;

        private int _lastVerb = Win32.OLEIVERB_SHOW;

        // Parent's moniker, if any.
        private IMoniker _fileMoniker = null;

        // If we are registered in the running-objects table.
        private int _rotCookie = 0;

        //private GChart _storage = new GChart();
        private ZedGraphControl _chart = new ZedGraphControl();

        #region Properties

        //internal bool Initializing {
        //    get { return _initializing; }
        //    private set { _initializing = value; }
        //}

        public int LastVerb {
            get { return _lastVerb; }
        }

        public ZedGraphControl Chart {
            get { return _chart; }
            set { _chart = value; }
        }

        public IList<IAdviseSink> Advises {
            get { return _advises; }
        }


        internal DataAdviseThread AdviseThread {
            get { return _dataAdviseThread; }
            private set { _dataAdviseThread = value; }
        }

        public IOleClientSite ClientSite {
            get { return _clientSite; }
            private set { _clientSite = value; }
        }

        public Form Form {
            get { return _form; }
            private set { _form = value; }
        }

        public GraphRenderer Renderer {
            get { return _renderer; }
            private set { _renderer = value; }
        }

        private Win32.tagSIZEL _extent = new Win32.tagSIZEL(15000, 10000);  // HIMETRIC

        public Win32.tagSIZEL Extent {
            get { return _extent; }
            private set {
                _extent = value;
                //NeedsSave();
                AdviseDataChanged();
                //NeedsSave();
            }
        }

        public int Cookie {
            get { return _rotCookie; }
        }

        public IMoniker Moniker {
            get {
                IMoniker item_moniker, new_moniker;
                int hr = Win32.CreateItemMoniker("!", Chart.Name, out item_moniker);
                Debug.Assert(hr == Win32.NOERROR);
                hr = Win32.CreateGenericComposite(_fileMoniker, item_moniker, out new_moniker);
                Debug.Assert(hr == Win32.NOERROR);
                return new_moniker;
            }
            private set {
                _fileMoniker = value;
            }
        }


        #endregion

        /// <summary>
        /// Constructor used by COM.
        /// </summary>
        public GraphBox() {
            logger.Debug("GraphBox()");
            Init(true, null);
        }

        public GraphBox(string graphType) {
            logger.Debug("GraphBox(string graphType)");
            //Chart.GraphType = graphType;
            Init(true, null);
        }

        public GraphBox(string graphType, IMoniker moniker) {
            //Chart.GraphType = graphType;
            Init(true, moniker);
        }

        /// <summary>Copy an object for placing on the clipboard.</summary>
        public GraphBox(GraphBox box)
        {
            //Chart.GraphType = box.Chart.GraphType;
            
            //Chart.Extent = new Win32.tagSIZEL(box.Extent);

            Init(false, box.Moniker);
        }

        private void Init(bool hasForm, IMoniker moniker)
        {
            Moniker = moniker;
            Program.BoxCreated();
            Renderer = new GraphRenderer(this);
            AdviseThread = new DataAdviseThread();

            //if (hasForm) {
            //    Form = new GraphForm(this);
            //    Form.Visible = false;
            //}

            logger.Debug("constructed");
        }

        ~GraphBox()
        {
            logger.Debug("destroyed");

            if (AdviseThread != null)
                AdviseThread.Stop();

            Program.BoxDestroyed();
        }

        public void CloseBox() {
            Close();
        }

        public void Close() {
            // Tell the container to save our changes.
            logger.Debug("Close Box");

            logger.Debug("Close step 1 : ClientSiteSaveObject");
            ClientSiteSaveObject();

            logger.Debug("Close step 2 : ClientSiteOnShowWindow(false)");
            ClientSiteOnShowWindow(false);

            logger.Debug("Close step 3 : AdviseOnClose");
            AdviseOnClose();

            logger.Debug("Close step 4 : ROTUnregister(ref _rotCookie)");
            ROTUnregister(ref _rotCookie);

            // Disconnect the container.
            logger.Debug("Close step 5 : Disconnecting object");
            Win32.CoDisconnectObject(this, 0);
        }


        internal int MiscStatus(int aspect) {
            int misc = Win32.OLEMISC_CANTLINKINSIDE;
            if (aspect == (int)DVASPECT.DVASPECT_CONTENT)
                misc |= Win32.OLEMISC_RECOMPOSEONRESIZE;
            return misc;
        }


        internal void FileRenamed(IMoniker fileMoniker) {
            Moniker = fileMoniker;

            if (_rotCookie != 0)
                ROTRegisterAsRunning();

            AdviseOnRename();
        }

        #region Advisements

        private void AdviseOnRename() {
            foreach (IAdviseSink sink in new List<IAdviseSink>(Advises)) {
                if (sink != null) {
                    logger.Debug("Calling IAdviseSink.OnRename()");

                    sink.OnRename(Moniker);
                }
            }
        }

        private void AdviseOnClose() {
            foreach (IAdviseSink sink in new List<IAdviseSink>(Advises)) {
                if (sink != null) {
                    logger.Debug("Calling IAdviseSink.OnClose()");
                    try {
                        // I have observed that Word does not call Unadvise
                        // when it closes, so we get various errors out of
                        // this.
                        sink.OnClose();
                    } catch (Exception e) {
                        logger.Error("sink.OnClose() failed: " + e.Message);
                    }
                }
            }
        }

        void AdviseOnSave() {
            foreach (IAdviseSink sink in Advises) {
                if (sink != null) {
                   logger.Debug("Calling IAdviseSink.OnSave()");
                   
                   sink.OnSave();
                }
            }
        }

        public void AdviseDataChanged() {
            if (AdviseThread != null) {
                AdviseThread.Invoke("SendOnDataChanging", delegate() {
                    AdviseThread.Holder.SendOnDataChange(this, 0, 0);
                });
            }
            if (_rotCookie != 0) {
                System.Runtime.InteropServices.ComTypes.FILETIME ft = new System.Runtime.InteropServices.ComTypes.FILETIME();
                Win32.CoFileTimeNow(out ft);
                Win32.GetROT().NoteChangeTime(_rotCookie, ref ft);
            }
        }

        private void ClientSiteSaveObject() {
            if (ClientSite != null) {
                logger.Debug("Calling IClientSite.SaveObject()");
                
                ClientSite.SaveObject();
            }
        }

        internal void ClientSiteOnShowWindow(bool shown) {
            if (ClientSite != null) {
                logger.Debug("Calling IClientSite.OnShowWindow({0})", shown);
                
                ClientSite.OnShowWindow(shown);
            }
        }

        #endregion

        #region ROT

        internal static void ROTUnregister(ref int cookie) {
            // Revoke any existing file moniker. p988
            IRunningObjectTable rot = Win32.GetROT();
            if (0 != cookie) {
                rot.Revoke(cookie);
                cookie = 0;
            }
        }

        internal void ROTRegisterAsRunning() {
            // This will unregister and reregister redundantly in some cases.
            ROTRegisterAsRunning(Moniker, this, ref _rotCookie, typeof(IOleObject));
        }

        private void ROTRegisterAsRunning(IMoniker new_moniker, object o, ref int rot_cookie, Type intf) {
            // Revoke any existing file moniker. p988
            ROTUnregister(ref rot_cookie);
            
            // Register the moniker in the running object table (ROT).
            logger.Debug("Registering {0} in ROT", Win32.GetDisplayName(new_moniker));
            IRunningObjectTable rot = Win32.GetROT();
            // This flag solved a terrible problem where Word would stop
            // communicating after its first call to GetObject().
            rot_cookie = rot.Register(1 /*ROTFLAGS_REGISTRATIONKEEPSALIVE*/, o, new_moniker);
        }

        #endregion

        #region COM IDataObject Members

        public int DAdvise(ref FORMATETC pFormatetc, ADVF advf, IAdviseSink adviseSink, out int connection) {
            //using (new IndentLog("IDataObject.DAdvise {0}, {1}", Log.FormatEtcToString(pFormatetc), advf)) {
            logger.Debug("IDataObject.DAdvise {0}, {1}", Util.FormatEtcToString(pFormatetc), advf);
            try {
                if (pFormatetc.cfFormat != 0) {  // don't check wildcard
                    int res = Renderer.QueryGetData(ref pFormatetc);
                    if (res != Win32.S_OK) {
                        connection = 0;
                        return res;
                    }
                }
                FORMATETC etc = pFormatetc;
                int conn = 0;
                AdviseThread.Invoke("Advising", delegate() {
                    AdviseThread.Holder.Advise(this, ref etc, advf, adviseSink, out conn);
                });
                connection = conn;
                return Win32.NOERROR;
            } catch (Exception e) {
                logger.Error("DAdvise occured an exception", e);
                throw;
            }
           // }
        }

        public void DUnadvise(int connection) {
            logger.Debug("IDataObject.DUnadvise {0}", connection);
            //using (new IndentLog("IDataObject.DUnadvise {0}", connection)) {
            try {
                AdviseThread.Invoke("Unadvising", delegate() {
                    AdviseThread.Holder.Unadvise(connection);
                });
            } catch (Exception e) {
                logger.Error("DUnadvise occured an exception.", e);
                throw;
            }
            //}
        }

        public int EnumDAdvise(out IEnumSTATDATA enumAdvise) {
            logger.Debug("IDataObject.EnumDAdvise -> OLE_E_ADVISENOTSUPPORTED");
            // Nobody implements this damn thing.
            enumAdvise = null;
            return Win32.OLE_E_ADVISENOTSUPPORTED;
        }

        public IEnumFORMATETC EnumFormatEtc(DATADIR direction) {
            logger.Debug("IDataObject.EnumFormatEtc");
            //using (new IndentLog("IDataObject.EnumFormatEtc")) {
            try {
                // We only support GET
                if (DATADIR.DATADIR_GET == direction)
                    return Renderer.EnumFormatEtc();
            } catch (Exception e) {
                logger.Error("EnumFormatEtc occured an exception.", e);
                throw;
            }

            logger.Debug("NOT IMPLEMENTED!");
            throw new NotImplementedException("OLE_S_USEREG");
            //}
        }

        public int GetCanonicalFormatEtc(ref FORMATETC formatIn, out FORMATETC formatOut) {
            logger.Debug("IDataObject.GetCanonicalFormatEtc {0}", Util.FormatEtcToString(formatIn));
            
            formatOut = formatIn;
            
            return Win32.DV_E_FORMATETC;
        }

        public void GetData(ref FORMATETC format, out STGMEDIUM medium) {
            logger.Debug("IDataObject.GetData({0})", Util.FormatEtcToString(format));

            //using (new IndentLog("IDataObject.GetData({0})", Log.FormatEtcToString(format))) {
                Renderer.GetData(ref format, out medium);
            //}
        }

        public void GetDataHere(ref FORMATETC format, ref STGMEDIUM medium) {
            logger.Debug("IDataObject.GetDataHere({0})", Util.ClipboardFormatName(format.cfFormat));
            //using (new IndentLog() {
            Renderer.GetDataHere(ref format, ref medium);
            //}
        }

        public int QueryGetData(ref FORMATETC format) {
            logger.Debug("IDataObject.QueryGetData({0})", Util.ClipboardFormatName(format.cfFormat));
            //using (new IndentLog("IDataObject.QueryGetData({0})", Log.ClipboardFormatName(format.cfFormat))) {
                try {
                    return Renderer.QueryGetData(ref format);
                } catch (Exception e) {
                    logger.Error("QueryGetData occured an exception", e);
                    throw;
                }
            //}
        }

        /// <summary>
        /// Sets data in the specified format into storage.
        /// </summary>
        /// <param name="formatIn">The format of the data.</param>
        /// <param name="medium">The data.</param>
        /// <param name="release">If true, ownership of the medium's memory will be transferred
        /// to this object. If false, a copy of the medium will be created and maintained, and
        /// the caller is responsible for the memory of the medium it provided.</param>
        public void SetData(ref FORMATETC formatIn, ref STGMEDIUM medium, bool release) {
            logger.Debug("IDataObject.SetData - NOT SUPPORTED!");
            throw new NotSupportedException();
        }

        #endregion

        #region COM IOleObject Members

        /// <summary>
        /// Informs an embedded object of its display location, 
        /// called a "client site," within its container.
        /// </summary>
        /// <param name="pClientSite"></param>
        /// <returns></returns>
        public int SetClientSite(IOleClientSite pClientSite) {
            logger.Debug("IOleObject.SetClientSite");
            ClientSite = pClientSite;
            return Win32.NOERROR;
        }

        public IOleClientSite GetClientSite() {
            logger.Debug("IOleObject.GetClientSite");
            Debug.Assert(ClientSite != null);
            return ClientSite;
        }

        public int SetHostNames(string szContainerApp, string szContainerObj) {
            logger.Debug("IOleObject.SetHostNames({0}, {1})", szContainerApp, szContainerObj);
            //using (new IndentLog("IOleObject.SetHostNames({0}, {1})", szContainerApp, szContainerObj)) {
            try {
                //if (Form != null) {
                Program.mainThread.BeginInvoke(delegate() {
                    Program.SetAsEmbedded(szContainerApp, szContainerObj);

                    if (logger is Log) {
                        ((Log)logger).SetTitle(szContainerObj + " in " + szContainerApp);
                    }
                });
                //}
                return Win32.NOERROR;
            } catch (Exception e) {
                logger.Error("SetHostNames occured an exception.", e);
                throw;
            }
            //}
        }

        /// <summary>
        /// Changes an embedded object from the running to the loaded state. 
        /// Disconnects a linked object from its link source.
        /// </summary>
        /// <param name="dwSaveOption">
        /// Indicates whether the object is to be saved as part of the transition to the loaded state. 
        /// Valid values are taken from the enumeration.<see cref="Gildata.Word.Graph.Win32.tagOLECLOSE"/></param>
        /// <returns>This method returns S_OK on success. or return OLE_E_PROMPTSAVECANCELLED, which means 
        /// that the user was prompted to save but chose the Cancel button from the prompt message box.</returns>
        public int Close(int dwSaveOption) {
            logger.Debug("IOleObject.Close({0})", dwSaveOption);
            //using (new IndentLog("IOleObject.Close({0})", dwSaveOption)) {
            try {
                bool save = false, cancelled = false;

                switch (dwSaveOption) {
                    case (int)Win32.tagOLECLOSE.OLECLOSE_SAVEIFDIRTY:
                        save = true;
                        break;
                    case (int)Win32.tagOLECLOSE.OLECLOSE_PROMPTSAVE:
                        Program.mainThread.Invoke(delegate() {
                            // If asked to prompt, do so only if dirty: if we get a YES, save as
                            // usual and close.  On NO, just close.  On CANCEL, return
                            // OLE_E_PROMPTSAVECANCELLED.
                            System.Windows.Forms.DialogResult r;
                            r = System.Windows.Forms.MessageBox.Show("Save?", "Box", System.Windows.Forms.MessageBoxButtons.YesNoCancel);
                            switch (r) {
                                case System.Windows.Forms.DialogResult.Yes:
                                    save = true;
                                    break;
                                case System.Windows.Forms.DialogResult.Cancel:
                                    cancelled = true;
                                    break;
                            }
                        });
                        break;
                    case (int)Win32.tagOLECLOSE.OLECLOSE_NOSAVE:
                        break;
                    default:
                        break;
                }
                
                if (cancelled) {
                    logger.Debug("-> OLE_E_PROMPTSAVECANCELLED");
                    return Win32.OLE_E_PROMPTSAVECANCELLED;
                }

                if (save) {
                    ClientSiteSaveObject();
                    AdviseOnSave();
                }

                if (Form != null) {
                    Program.mainThread.Invoke(delegate() {
                        // Avoids form OnClose handler.
                        Form.Dispose();
                    });
                    Form = null;
                }
                // Regardless of whether the form has been shown we must
                // do all the normal shutdown actions.  (e.g. WinWord 2007)
                Close();
                logger.Debug("-> NOERROR");
                return Win32.NOERROR;
            } catch (Exception e) {
                logger.Error("Close occured an exception.", e);
                throw;
            }
           // }
        }

        
        /// <summary>
        /// Notifies an object of its container's moniker, 
        /// the object's own moniker relative to the container, 
        /// or the object's full moniker.
        /// </summary>
        /// <param name="dwWhichMoniker">The moniker is passed in pmk. 
        /// Possible values are from the enumeration OLEWHICHMK.</param>
        /// <param name="pmk"></param>
        /// <returns></returns>
        public int SetMoniker(int dwWhichMoniker, object pmk) {
            logger.Debug("IOleObject.SetMoniker -> NotImplementedException");
            throw new NotImplementedException();
        }

        /// <summary>
        /// Retrieves a moniker for the object's client site. 
        /// An object can force the assignment of its own or its 
        /// container's moniker by specifying a value for dwAssign.
        /// <remarks>If the OLEGETMONIKER_FORCEASSIGN flag causes a container 
        /// to create a moniker for the object, 
        /// the container should notify the object by calling the 
        /// IOleObject.GetMoniker method.</remarks>
        /// </summary>
        /// <param name="dwAssign">Specifies whether to get a moniker only if one already exists, 
        /// force assignment of a moniker, create a temporary moniker, 
        /// or remove a moniker that has been assigned. In practice, 
        /// you will usually request that the container force assignment of the moniker. 
        /// Possible values are taken from the OLEGETMONIKER enumeration.</param>
        /// <param name="dwWhichMoniker"></param>
        /// <param name="moniker"></param>
        /// <returns></returns>
        public int GetMoniker(int dwAssign, int dwWhichMoniker, out object moniker) {
            logger.Debug("IOleObject.GetMoniker -> NotImplementedException");
            throw new NotImplementedException();
        }

        /// <summary>
        /// Initializes a newly created object with data from a specified data object, 
        /// which can reside either in the same container or on the Clipboard.
        /// See http://msdn.microsoft.com/en-us/library/ms688510(VS.85).aspx
        /// </summary>
        /// <param name="pDataObject">Pointer to the IDataObject interface on the data object 
        /// from which the initialization data is to be obtained. 
        /// This parameter can be NULL, which indicates that the caller 
        /// wants to know if it is worthwhile trying to send data; 
        /// that is, whether the container is capable of initializing 
        /// an object from data passed to it. 
        /// The data object to be passed can be based on either the current 
        /// selection within the container document or on data transferred to 
        /// the container from an external source.</param>
        /// <param name="fCreation">TRUE indicates the container is inserting a new object inside itself 
        /// and initializing that object with data from the current selection; 
        /// FALSE indicates a more general programmatic data transfer, 
        /// most likely from a source other than the current selection.</param>
        /// <param name="dwReserved">This parameter is reserved and must be zero.</param>
        /// <returns>This method returns S_OK if pDataObject is not NULL, 
        /// the object successfully attempted to initialize itself from the provided data; 
        /// if pDataObject is NULL, the object is able to attempt a successful initialization.. 
        /// Other possible return values include the following.</returns>
        public int InitFromData(IDataObject pDataObject, int fCreation, int dwReserved) {
            logger.Debug("IOleObject.InitFromData -> NotImplementedException");
            
            throw new NotImplementedException();
        }

        /// <summary>
        /// Thismethod retrieves a data object that contains the current contents of 
        /// the embedded object on which this method is called. Using the pointer to this data object, 
        /// it is possible to create an embedded object with the same data as the original.
        /// </summary>
        /// <param name="dwReserved">Reserved for future use; set to zero.</param>
        /// <param name="data">Address of IDataObject* pointer variable that receives the interface pointer 
        /// to the data object. If an error occurs, *ppDataObject must be set to NULL.
        /// Each time an object receives a call to GetClipboardData, it must increase 
        /// the reference count on *ppDataObject.
        /// The caller is responsible to call IUnknown.Release when it is done with *ppDataObject.</param>
        /// <returns>S_OK,The data transfer object is returned.
        /// E_NOTIMPL,GetClipboardData is not supported.
        /// OLE_E_NOTRUNNING,The object is not running.</returns>
        public int GetClipboardData(int dwReserved, out IDataObject data) {
            logger.Debug("IOleObject.GetClipboardData");
            //using (new IndentLog("IOleObject.GetClipboardData")) {
            try {
                GraphBox new_box = new GraphBox(this);
                data = new_box;
                return Win32.S_OK;
            } catch (Exception e) {
                logger.Error("GetClipboardData occured an exception.", e);
                throw;
            }
            //}
        }

        /// <summary>
        /// Requests that an object perform an action in response to an end-user's action. 
        /// The possible actions are enumerated for the object in <see cref="Gildata.Word.Graph.GraphBox.EnumVerbs"/>.
        /// See http://msdn.microsoft.com/en-us/library/ms694508(VS.85).aspx
        /// </summary>
        /// <param name="iVerb">Number assigned to the verb in the <see cref="Gildata.Word.Graph.GraphBox.Win32.tagOLEVERB"/> 
        /// structure returned by <see cref="Gildata.Word.Graph.GraphBox.EnumVerbs"/>.</param>
        /// <param name="lpmsg">Pointer to the MSG structure describing the event (such as a double-click) 
        /// that invoked the verb. The caller should pass the MSG structure unmodified, 
        /// without attempting to interpret or alter the values of any of the structure members.
        /// Not used.</param>
        /// <param name="pActiveSite">Pointer to the IOleClientSite interface on the object's active client site, 
        /// where the event occurred that invoked the verb.</param>
        /// <param name="lindex">This parameter is reserved and must be zero.</param>
        /// <param name="hwndParent">Handle of the document window containing the object. 
        /// This and lprcPosRect together make it possible to open a temporary window for an object, 
        /// where hwndParent is the parent window in which the object's window is to be displayed, 
        /// and lprcPosRect defines the area available for displaying the object window within that parent. 
        /// A temporary window is useful, for example, 
        /// to a multimedia object that opens itself for playback but not for editing.</param>
        /// <param name="lprcPosRect">Pointer to the RECT structure containing the coordinates, 
        /// in pixels, that define an object's bounding rectangle in hwndParent. 
        /// This and hwndParent together enable opening multimedia objects for playback but not for editing.</param>
        /// <returns>This method returns S_OK on success.</returns>
        public int DoVerb(int iVerb, IntPtr lpmsg, IOleClientSite pActiveSite, int lindex, IntPtr hwndParent, Win32.COMRECT lprcPosRect) {
            logger.Debug("IOleObject.DoVerb {0}", iVerb);
            //using (new IndentLog("IOleObject.DoVerb {0}", iVerb)) {
            try {
                // I saw OLEIVERB_HIDE come in as 253.  Perhaps a unsigned
                // byte conversion happening somewhere.
                if (iVerb >= 250 && iVerb <= 255) {
                    int new_iverb = iVerb - 256;
                    logger.Debug("Fixing iVerb: {0} -> {1}", iVerb, new_iverb);
                    iVerb = new_iverb;
                }

                _lastVerb = iVerb;

                switch (iVerb) {
                    case Win32.OLEIVERB_SHOW:
                        logger.Info("Do verb : OLEIVERB_SHOW");
                        //logger.Info("Program.mainThread is null? {0}", Program.mainThread == null);

                        Program.mainThread.Invoke(delegate() {
                            //Program.mainForm.Show();
                            logger.Info("delegated");

                            if (Form == null || Form.Disposing) {
                                logger.Info("new GraphForm(this);");
                                Form = new GraphForm(this);
                            }

                            logger.Info("Form.Show();");

                            Form.Show();
                            Form.Activate();
                            Win32.SetForegroundWindow(Form.Handle);
                        });

                        if (pActiveSite != null) {
                            logger.Debug("ClientSite.ShowObject()");
                            
                            pActiveSite.ShowObject();
                            
                            ClientSiteOnShowWindow(true);
                        }

                        return Win32.NOERROR;
                    case Win32.OLEIVERB_UPDATE:
                        logger.Info("Do verb : OLEIVERB_UPDATE");
                        AdviseDataChanged();
                        //Close();
                        return Win32.NOERROR;
                    //case Win32.OLEIVERB_SHOW:
                    case Win32.OLEIVERB_PRIMARY:
                        logger.Info("Do verb : OLEIVERB_PRIMARY");

                        Program.mainThread.Invoke(delegate() {
                            //Program.mainForm.Show();
                            if (Form == null || Form.Disposing) {
                                Form = new GraphForm(this);
                            }

                            Form.Show();
                            Form.Activate();
                            Win32.SetForegroundWindow(Form.Handle);
                        }); 

                        if (pActiveSite != null) {
                            logger.Debug("ClientSite.ShowObject()");
                            
                            pActiveSite.ShowObject();
                            
                            ClientSiteOnShowWindow(true);
                        }

                        break;
                    case Win32.OLEIVERB_PROPERTIES:
                        logger.Info("Do verb : OLEIVERB_PROPERTIES");
                        

                        Program.mainThread.Invoke(delegate() {
                            //Form1 form = new Form1();
                            //IOleInPlaceActiveObject obj = this;

                            //form.SetActiveObject(ref obj, "Obje");
                            //form.Show();

                            //Program.mainForm.Show();
                            if (Form == null) {
                                Form = new GraphProperty(this);
                            }

                            Form.Show();
                            Form.Activate();
                            Win32.SetForegroundWindow(Form.Handle);
                        }); 

                        if (pActiveSite != null) {
                            logger.Debug("ClientSite.ShowObject()");
                            
                            pActiveSite.ShowObject();
                            
                            ClientSiteOnShowWindow(true);
                        }

                        break;
                    case Win32.OLEIVERB_HIDE:
                        logger.Info("OLEIVERB_HIDE");
                        //Program.mainThread.Invoke(delegate() {
                        //    Form.Hide();
                        //});
                        ClientSiteOnShowWindow(false);
                        break;
                    default:
                        Debug.Assert(false, "Unexpected verb: " + iVerb.ToString());
                        return Win32.OLEOBJ_S_INVALIDVERB;
                }
                logger.Debug("-> NOERROR");
                return Win32.NOERROR;
            } catch (Exception e) {
                logger.Error("DoVerb occured an exception.", e);
                throw;
            }
        }

        /// <summary>
        /// Exposes a pull-down menu listing the 
        /// verbs available for an object in ascending order by verb number.
        /// </summary>
        /// <param name="e"></param>
        /// <returns></returns>
        public int EnumVerbs(out IEnumOLEVERB e) {
            logger.Debug("IOleObject.EnumVerbs -> OLE_S_USEREG");
            e = null;
            return Win32.OLE_S_USEREG;
        }

        public int OleUpdate() {
            logger.Debug("IOleObject.OleUpdate -> NOERROR");
            // We are always updated since we don't contain.
            return Win32.NOERROR;
        }

        public int IsUpToDate() {
            logger.Debug("IOleObject.IsUpToDate -> NOERROR");
            // We are always updated since we don't contain.
            return Win32.NOERROR;
        }

        /// <summary>
        /// Retrieves an object's class identifier, 
        /// the CLSID corresponding to the string identifying the object to an end user.
        /// </summary>
        /// <param name="pClsid"></param>
        /// <returns></returns>
        public int GetUserClassID(ref Guid pClsid) {
            logger.Debug("IOleObject.GetUserClassID");
            if (Program.isEmbedded)
                pClsid = CLSID;
            //else
            //    pClsid = BoxesDoc.ClsID;
            return Win32.NOERROR;
        }

        /// <summary>
        /// Retrieves the user-type name of an object for display 
        /// in user-interface elements such as menus, list boxes, and dialog boxes.
        /// </summary>
        /// <param name="dwFormOfType">he form of the user-type name to be presented to users. 
        /// Possible values are obtained from the <see cref="Gildata.Word.Graph.Win32.tagUSERCLASSTYPE"/> enumeration.</param>
        /// <param name="userType">Address of LPOLESTR pointer variable that receives a pointer to 
        /// the user type string. The caller must free pszUserType using the current IMalloc instance. 
        /// If an error occurs, the implementation must set pszUserType to NULL.</param>
        /// <returns></returns>
        public int GetUserType(int dwFormOfType, out string userType) {
            logger.Debug("IOleObject.GetUserType -> OLE_S_USEREG");
            // I suppose it is worthwhile to include this information in the
            // registry because it allows handlers to know our type without
            // creating an instance of this class.  That being the case we
            // just refer callers to the registry.
            userType = USER_TYPE;
            return Win32.OLE_S_USEREG;
        }

        /// <summary>
        /// Informs an object of how much display space its container has assigned it.
        /// </summary>
        /// <param name="dwDrawAspect">DWORD that describes which form, or "aspect," 
        /// of an object is to be displayed. 
        /// The object's container obtains this value from the enumeration DVASPECT 
        /// (refer to the FORMATETC enumeration). 
        /// The most common aspect is DVASPECT_CONTENT, 
        /// which specifies a full rendering of the object within its container. 
        /// An object can also be rendered as an icon, a thumbnail version for display in a browsing tool, 
        /// or a print version, which displays the object as it would be rendered using the File Print command.</param>
        /// <param name="pSizel">Pointer to the size limit for the object.</param>
        /// <returns>This method returns S_OK on success.
        /// E_FAIL,The operation failed.
        /// OLE_E_NOTRUNNING,The object is not running.
        /// </returns>
        public int SetExtent(int dwDrawAspect, Win32.tagSIZEL pSizel) {
            logger.Debug("IOleObject.SetExtent({0}x{1})", pSizel.cx, pSizel.cy);
            //using (new IndentLog("IOleObject.SetExtent({0}x{1})", pSizel.cx, pSizel.cy)) {
                try {
                    if ((dwDrawAspect & (int)DVASPECT.DVASPECT_CONTENT) == 0)
                        return Win32.E_FAIL;

                    Extent = new Win32.tagSIZEL(pSizel.cx, pSizel.cy);
                    //Log.Add("Form != null = " + (Form != null).ToString());

                    //if (Form != null) {
                    //    Program.mainThread.Invoke(delegate() {
                    //        Form.BoxUpdated();
                    //    });
                    //}
                    // Changes to size should be preserved.
                    ClientSiteSaveObject();
                    //Close();
                    return Win32.S_OK;
                } catch (Exception e) {
                    logger.Error("SetExtent occured an exception.", e);
                    throw;
                }
            //}
        }

        /// <summary>
        /// Retrieves a running object's current display size.
        /// </summary>
        /// <param name="dwDrawAspect">The aspect of the object whose limit is to be retrieved; 
        /// the value is obtained from the enumerations DVASPECT and from DVASPECT2. 
        /// Note that newer objects and containers that support optimized drawing interfaces 
        /// support the DVASPECT2 enumeration values. Older objects and containers that do not 
        /// support optimized drawing interfaces may not support DVASPECT2. 
        /// The most common value for this method is DVASPECT_CONTENT, 
        /// which specifies a full rendering of the object within its container.</param>
        /// <param name="pSizel">Pointer to where the object's size is to be returned.</param>
        /// <returns>This method returns S_OK on success.
        /// E_INVALIDARG, The supplied dwDrawAspect value is invalid.
        /// </returns>
        public int GetExtent(int dwDrawAspect, Win32.tagSIZEL pSizel) {
            logger.Debug("IOleObject.GetExtent");
            
            if ((dwDrawAspect & (int)DVASPECT.DVASPECT_CONTENT) == 0) {
                logger.Debug("-> E_FAIL");
                return Win32.E_FAIL;
            }

            pSizel.cx = Extent.cx;
            pSizel.cy = Extent.cy;

            return Win32.S_OK;
        }

        /// <summary>
        /// Establishes an advisory connection between a compound document object and 
        /// the calling object's advise sink, 
        /// through which the calling object receives notification when the compound document object 
        /// is renamed, saved, or closed.
        /// </summary>
        /// <param name="pAdvSink">Pointer to the IAdviseSink interface on the advise sink of the calling object.</param>
        /// <param name="cookie">Pointer to a token that can be passed to 
        /// IOleObject.Unadvise to delete the advisory connection.</param>
        /// <returns>This method returns S_OK on success.
        /// E_OUTOFMEMORY,Insufficient memory available for this operation.</returns>
        public int Advise(IAdviseSink pAdvSink, out int cookie) {
            logger.Debug("IOleObject.Advise");
            //using (new IndentLog("IOleObject.Advise")) {
                try {
                    // We would have liked to use CreateOleAdviseHolder, but it crashes
                    // when accessed from multiple threads, and we seem to get every
                    // call from a different thread!
                    cookie = Advises.Count;
                    Advises.Add(pAdvSink);
                    
                    return Win32.S_OK;
                } catch (Exception e) {
                    logger.Error("Advise occured an exception.", e);
                    throw;
                }
            //}
        }

        /// <summary>
        /// Deletes a previously established advisory connection.
        /// </summary>
        /// <param name="dwConnection">Contains a token of nonzero value, which was previously returned from 
        /// IOleObject.Advise through its pdwConnection parameter.</param>
        /// <returns>This method returns S_OK on success.
        /// E_FAIL,The operation failed.
        /// OLE_E_NOCONNECTION,dwConnection does not represent a valid advisory connection.</returns>
        public int Unadvise(int dwConnection) {
            logger.Debug("IOleObject.Unadvise");
            //using (new IndentLog("IOleObject.Unadvise")) {
                try {
                    //oleAdviseHolder.Unadvise(dwConnection);
                    Debug.Assert(Advises[dwConnection] != null);
                    Advises[dwConnection] = null;

                    return Win32.S_OK;
                } catch (Exception e) {
                    logger.Error("Unadvise occured an exception.", e);
                    throw;
                }
            //}
        }

        /// <summary>
        /// Retrieves a pointer to an enumerator that can be used to enumerate the advisory connections 
        /// registered for an object, 
        /// so a container can know what to release prior to closing down.
        /// </summary>
        /// <param name="e">Address of IEnumSTATDATA pointer variable that receives the interface pointer 
        /// to the enumerator object. If the object does not have any advisory connections or 
        /// if an error occurs, the implementation must set ppenumAdvise to NULL. 
        /// Each time an object receives a successful call to IOleObject.EnumAdvise, 
        /// it must increase the reference count on ppenumAdvise. 
        /// It is the caller's responsibility to call Release when it is done with the ppenumAdvise.</param>
        /// <returns>This method returns S_OK on success.
        /// E_NOTIMPL,IOleObject.EnumAdvise is not implemented.</returns>
        public int EnumAdvise(out IEnumSTATDATA e) {
            logger.Debug("IOleObject.EnumAdvise -> E_NOTIMPL");
            e = null;
            return Win32.E_NOTIMPL;
        }

        /// <summary>
        /// Retrieves the status of an object at creation and loading.
        /// </summary>
        /// <param name="dwAspect">The aspect of an object about which status information is being requested. 
        /// The value is obtained from the enumeration DVASPECT.</param>
        /// <param name="misc">Pointer to where the status information is returned. 
        /// This parameter cannot be NULL.</param>
        /// <returns>This method returns S_OK on success. </returns>
        public int GetMiscStatus(int dwAspect, out int misc) {
            logger.Debug("IOleObject.GetMiscStatus({0})", dwAspect);

            //using (new IndentLog("IOleObject.GetMiscStatus({0})", dwAspect)) {
                misc = MiscStatus(dwAspect);
                logger.Debug("misc -> {0}", misc);
                return Win32.NOERROR;
            //}
        }


        /// <summary>
        /// Specifies the color palette that the object 
        /// application should use when it edits the specified object.
        /// </summary>
        /// <param name="pLogpal">Pointer to a LOGPALETTE structure that specifies the recommended palette.</param>
        /// <returns>This method returns S_OK on success</returns>
        public int SetColorScheme(Win32.tagLOGPALETTE pLogpal) {
            logger.Debug("IOleObject.SetColorScheme -> NotImplementedException");
            throw new NotImplementedException();
        }

        #endregion

        #region IPersistStorage Members

        /// <summary>
        /// Retrieves the class identifier (CLSID) of the object.
        /// </summary>
        /// <param name="pClassID"></param>
        public void GetClassID(out Guid pClassID) {
            logger.Debug("IPersistStorage.GetClassID");
            pClassID = CLSID;
        }

        public int IsDirty() {
            //using (new IndentLog("IPersistStorage.IsDirty")) {
                // This should always return false when we are open for editing
                // because the container should have been notified about any
                // changes (Brockschmidt p883)
                return NeedsSave() ? Win32.S_OK : Win32.S_FALSE;
           // }
        }

        public void InitNew(IStorage pstg) {
            logger.Debug("IPersistStorage.InitNew");
           // using (new IndentLog("IPersistStorage.InitNew")) {
                // We don't need an IStorage except at Load/Save.
                //Storage.GraphType = "New";
            //}
            //Chart.GraphType = "GLine";
        }

        public int Load(IStorage pstg) {
            logger.Debug("IPersistStorage.Load");
            //using (new IndentLog("IPersistStorage.Load")) {
                // Read our XML data.
                byte[] buffer;

                try {
                    IStream istrm = pstg.OpenStream("GraphBox", IntPtr.Zero, (int)(Win32.STGM.READ | Win32.STGM.SHARE_EXCLUSIVE), 0);
                    try {
                        System.Runtime.InteropServices.ComTypes.STATSTG stat = new System.Runtime.InteropServices.ComTypes.STATSTG();
                        istrm.Stat(out stat, 1 /*STATFLAG_NONAME*/);
                        buffer = new byte[stat.cbSize];
                        istrm.Read(buffer, buffer.Length, IntPtr.Zero);

                        using (MemoryStream ms = new MemoryStream(buffer)) {
                            BinaryFormatter formatter = new BinaryFormatter();
                            MasterPane s = formatter.Deserialize(ms) as MasterPane;

                            if (s != null) {
                                this.Chart.MasterPane = s;
                            } else {
                                logger.Error(string.Format("无法将OLE对象中的字节流转换成GChart对象，字节长度：{0}", buffer.Length));
                            }
                        }
                    } catch (Exception ex) {
                        logger.Error("无法将OLE对象中的字节流转换成GChart对象", ex);
                    }
                    finally {
                        Marshal.ReleaseComObject(istrm);
                    }
                } catch (Exception ex) {
                    logger.Error("无法将OLE对象中的字节流转换成GChart对象", ex);
                } finally {
                    Marshal.ReleaseComObject(pstg);
                }

                return Win32.S_OK;
            //}
        }

        public void Save(IStorage pStgSave, bool fSameAsLoad) {
            logger.Debug("IPersistStorage.Save");
            //using (new IndentLog("IPersistStorage.Save")) {
            byte[] buffer;

            using (MemoryStream writer = new MemoryStream()) {
                BinaryFormatter formatter = new BinaryFormatter();

                formatter.Serialize(writer, Chart.MasterPane);
                logger.Debug("formatter.Serialize(writer, Chart);");
                writer.Position = 0;
                buffer = writer.GetBuffer();
            }

            try {
                Win32.WriteClassStg(pStgSave, CLSID);
                Win32.WriteFmtUserTypeStg(pStgSave, (uint)Win32.RegisterClipboardFormat("Box"), GraphBox.USER_TYPE);
                IStream istrm = pStgSave.CreateStream("GraphBox",
                                                      (int)(Win32.STGM.DIRECT | Win32.STGM.READWRITE | Win32.STGM.CREATE | Win32.STGM.SHARE_EXCLUSIVE), 0, 0);
                istrm.Write(buffer, buffer.Length, IntPtr.Zero);

                Marshal.ReleaseComObject(istrm);
            } catch (Exception ex) {
                logger.Error("无法将GChart对象写入OLE对象流中", ex);
            } finally {
                Marshal.ReleaseComObject(pStgSave);
            }
            
                //RememberLoadedValues();
            //}
        }

        /// <summary>
        /// Notifies the object that it can revert from NoScribble or HandsOff mode, 
        /// in which it must not write to its storage object, to Normal mode, in which it can. 
        /// The component object enters NoScribble mode when it receives an IPersistStorage.Save call.
        /// </summary>
        /// <param name="pStgNew">Points to the new storage object, 
        /// if different from the storage object prior to saving. 
        /// This pointer can be NULL if the current storage object does not change during the save operation. 
        /// If the object is in HandsOff mode, this parameter must be non-NULL.</param>
        public void SaveCompleted(IStorage pStgNew) {
            logger.Debug("IPersistStorage.SaveCompleted");
            //using (new IndentLog("IPersistStorage.SaveCompleted")) {
                // We only ever write in response to Save().
                AdviseOnSave();
            //}
        }

        public int HandsOffStorage() {
            logger.Debug("IPersistStorage.HandsOffStorage");
            return Win32.S_OK;
        }

        #endregion

        public void Update() {
            AdviseDataChanged();
        }

        bool NeedsSave() {
            return true;
        }

        #region IOleInPlaceObject Members

        public int GetWindow(ref IntPtr phwnd) {
            logger.Debug("GetWindow(ref IntPtr phwnd) {0}", phwnd);

            return Win32.S_FALSE;
        }

        public int ContextSensitiveHelp(bool fEnterMode) {
            logger.Debug("ContextSensitiveHelp(bool fEnterMode)");

            return Win32.S_FALSE;
        }

        public void InPlaceDeactivate() {
            logger.Debug("InPlaceDeactivate()");
        }

        public int UIDeactivate() {
            logger.Debug("UIDeactivate()");

            return Win32.S_FALSE;
        }

        public int SetObjectRects(ref Win32.RECT lprcPosRect, ref Win32.RECT lprcClipRect) {
            logger.Debug("SetObjectRects()");

            return Win32.S_FALSE;
        }

        public void ReactivateAndUndo() {
            logger.Debug("ReactivateAndUndo()");

        }

        #endregion


        #region IOleInPlaceActiveObject Members


        public int TranslateAccelerator(ref Message lpmsg) {
            logger.Debug("IOleInPlaceActiveObject.TranslateAccelerator()");
            return Win32.S_FALSE;
        }

        public int OnFrameWindowActivate(bool fActivate) {
            logger.Debug("IOleInPlaceActiveObject.OnFrameWindowActivate({0})", fActivate);
            return Win32.S_FALSE;
        }

        public int OnDocWindowActivate(bool fActivate) {
            logger.Debug("IOleInPlaceActiveObject.OnDocWindowActivate({0})", fActivate);
            return Win32.S_FALSE;
        }

        public int ResizeBorder(ref Win32.RECT prcBorder, ref IOleInPlaceUIWindow pUIWindow, bool fFrameWindow) {
            logger.Debug("IOleInPlaceActiveObject.ResizeBorder({0})", fFrameWindow);
            return Win32.S_FALSE;
        }

        public int EnableModeless(bool fEnable) {
            logger.Debug("IOleInPlaceActiveObject.EnableModeless({0})", fEnable);
            return Win32.S_FALSE;
        }

        #endregion
    }
}
